//
//  BleManager.m
//  leo
//
//  Created by 肖先森 on 15/12/9.
//  Copyright © 2015年 mdx. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>
#import "BleManager.h"

// multi device scan function
//#define CONNECT_SOLUTION_TYPE_SCAN YES
//
//#ifneq CONNECT_SOLUTION_TYPE_SCAN YES
//#define CONNECT_SOLUTION_TYPE_CONNECT YES
//#endif
#define ACTION_SERVECE_UUID @"0000ee01-0000-1000-8000-00805f9b34fb"
#define RANDOM_CHARACTERISTIC_UUID @"0000ff01-0000-1000-8000-00805f9b34fb"
#define RANDOM_DESCRIPTIOR_UUID @"00002902-0000-1000-8000-00805f9b34fb"
#define VERIFY_CHARACTERISTIC_UUID @"0000ff02-0000-1000-8000-00805f9b34fb"
#define ANTILOST_CHARACTERISTIC_UUID @"0000ff03-0000-1000-8000-00805f9b34fb"
#define FIX_DECODE_HEX_DATA @"1F2E3D4C"
#define NOTI_BYTE [{2}]
#define C2I(c) ((c >= '0' && c<='9') ? (c-'0') : ((c >= 'a' && c <= 'z') ? (c - 'a' + 10): ((c >= 'A' && c <= 'Z')?(c - 'A' + 10):(-1))))

@interface ConnectionListener ()
@end

@implementation ConnectionListener

-(void)onDeviceDisconnected:(NSObject *)cookie macAddress:(NSString *)macAddress {
}

-(void)onDeviceConnected:(NSObject *)cookie macAddress:(NSString *)macAddress {
}

@end

// Internal callback wrapper declaring
@interface CallbackSuporvisor : ConnectionListener

@property (assign, nonatomic) long lastUpdateTime;
@property (strong, nonatomic) NSString* macAddress;
@property (strong, nonatomic) ConnectionListener* rawCallback;
@property (strong, nonatomic) NSObject* cookie;
@property (strong, nonatomic) CBPeripheral* mPeripheral;

@end

@implementation CallbackSuporvisor
@end

// Manager declaring
@interface BleManager ()

@property (strong, nonatomic) NSMutableDictionary<NSString*, ConnectionListener*> *mCallbacks;
@property (assign) BOOL mScanScheduled;
@property (assign) BOOL mCheckScheduled;
@property (strong, nonatomic) CBCentralManager* mBluetoohManager;
@property (strong,nonatomic) NSMutableArray *peripherals;//连接的外围设备
@property (strong, nonatomic) NSString* userID;
@property (strong, nonatomic) NSData* sendData;
@property (nonatomic,strong)CBCharacteristic *txCharacteristic;
@end

@implementation BleManager

-(NSMutableArray *)peripherals{
    if(!_peripherals){
        _peripherals=[NSMutableArray array];
    }
    return _peripherals;
}

+(BleManager*) getInstance {
    static BleManager *instance = nil;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        instance = [[self allocWithZone:NULL] init];
        instance.mBluetoohManager = [[CBCentralManager alloc] initWithDelegate:instance queue:nil];
    });
    return instance;
}

+(BleManager*) getInstance :(BOOL)parameter{
    static BleManager *instance = nil;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        instance = [[self allocWithZone:NULL] init];
    });
    if (instance.mBluetoohManager == NULL) {
        instance.mBluetoohManager = [[CBCentralManager alloc] initWithDelegate:instance queue:nil];
    }
    return instance;
}

-(void)centralManagerDidUpdateState:(CBCentralManager *)central {
    // TODO do what?
    switch (central.state) {
        case CBPeripheralManagerStatePoweredOn:
            [self.mBluetoohManager scanForPeripheralsWithServices:nil options:nil];
            break;
        default:
            NSLog(@"unSupport or disable BLE");
            break;
    }
}

-(ConnectionListener*) createWrapper:(NSString*)macAddress withListener:(ConnectionListener*) listener wrapCookie:(NSObject*) cookie {
    if (listener == nil) return nil;
    
    ConnectionListener* wrapper = [self.mCallbacks valueForKey:macAddress];
    if (wrapper == nil) {
        wrapper = [[CallbackSuporvisor alloc] init];
        ((CallbackSuporvisor*) wrapper).rawCallback = listener;
        ((CallbackSuporvisor*) wrapper).macAddress = macAddress;
    }
    ((CallbackSuporvisor*) wrapper).cookie = nil;
    return wrapper;
}

-(void)registerConnectionListener:(ConnectionListener *)listener macAddress:(NSString *)macAddress userID:(NSString *)userID withCookie:(NSObject *)cookie {
    // Wrap internal callback, if nil returned, means failed when wrapping,
    // maybe parameters is invalid, or callback already exists.
    ConnectionListener *l = [self createWrapper:macAddress withListener:listener wrapCookie:cookie];
    self.userID = userID;
    if (l == nil) return;
    if (self.mCallbacks == NULL) {
        self.mCallbacks = [[NSMutableDictionary alloc]init];
    }
    [self.mCallbacks setObject:l forKey:macAddress];

    // if (self.mScanScheduled) return;
    // [self scheduleNextScan];
}

-(void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(nonnull CBPeripheral *)peripheral advertisementData:(nonnull NSDictionary<NSString *,id> *)advertisementData RSSI:(nonnull NSNumber *)RSSI {
    //must safe BLE peripheral(it need use strong pointer to store peripheral do callback normally before destory)
    if (peripheral) {
       for (NSString* macAddress in self.mCallbacks) {
            if ([peripheral.name containsString:[self convertSymbolForMacAddress:macAddress]]) {
                [self.mBluetoohManager stopScan];
                CallbackSuporvisor* wrapper = (CallbackSuporvisor*)[self.mCallbacks objectForKey:macAddress];
                NSLog(@"didDiscoverPeripheral , call connectPeripheral name = %@",peripheral.name);
                wrapper.mPeripheral = peripheral;
                [self.mBluetoohManager connectPeripheral:peripheral options:nil];
            }
       }
    }
}

-(void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    NSLog(@"%@",peripheral.name);
    for (NSString* macAddress in self.mCallbacks) {
        CallbackSuporvisor* wrapper = (CallbackSuporvisor*)[self.mCallbacks objectForKey:macAddress];
        if ([peripheral.name containsString:[self convertSymbolForMacAddress:wrapper.macAddress]]) {
            //[wrapper.rawCallback onDeviceConnected:wrapper.cookie macAddress:wrapper.macAddress];
            peripheral.delegate = self;
            [peripheral discoverServices:nil];
        }
    }
}

-(void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    for (CBService* service in peripheral.services) {
        NSLog(@"service.UUID = %@ , upper = %@",service.UUID , ACTION_SERVECE_UUID.uppercaseString);
        if ([service.UUID isEqual:[CBUUID UUIDWithString:ACTION_SERVECE_UUID.uppercaseString]]) {
            [peripheral discoverCharacteristics:nil forService:service];
        }
    }
}

-(void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(nonnull CBService *)service error:(nullable NSError *)error {
    for (CBCharacteristic* character in service.characteristics) {
        NSLog(@"characteristic  = %@ , characteristic.UUID = %@ , service.UUID = %@",character,character.UUID,service.UUID);
        if ([character.UUID isEqual:[CBUUID UUIDWithString:RANDOM_CHARACTERISTIC_UUID.uppercaseString]]) {
            [peripheral setNotifyValue:YES forCharacteristic:character];
            //[peripheral discoverDescriptorsForCharacteristic:character];
        }else if([character.UUID isEqual:[CBUUID UUIDWithString:VERIFY_CHARACTERISTIC_UUID.uppercaseString]]){
            //[peripheral readValueForCharacteristic:character];
            NSLog(@"discover VERIFY_CHARACTERISTIC_UUID , so need store it.");
            self.txCharacteristic = character;
        }
    }
    NSLog(@"finish didDiscoverCharacter");
}

-(void)peripheral:(CBPeripheral *)peripheral didDiscoverDescriptorsForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    for (CBDescriptor* descriptor in characteristic.descriptors) {
        NSLog(@"descriptor.UUID = %@",descriptor.UUID);
        if ([descriptor.UUID isEqual:[CBUUID UUIDWithString:RANDOM_DESCRIPTIOR_UUID.uppercaseString]]) {
            Byte byte[] = {2};
            [peripheral writeValue:[[NSData alloc]initWithBytes:byte length:1] forDescriptor:descriptor];
        }
    }
}

-(void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error {
    NSLog(@"characteristic.UUID = %@ , characteristic.value = %@",characteristic.UUID,characteristic.value);
    NSString *value = [[NSString stringWithFormat:@"%@",characteristic.value]stringByReplacingOccurrencesOfString:@" " withString:@""];
    if ([@"5a5a" isEqualToString:[value substringWithRange:NSMakeRange(1, 4)]]) {
        NSString *random = [value substringWithRange:NSMakeRange(5, 8)];
        NSData *sendData = [self encodeSendDataByRadomData:random];
        self.sendData = sendData;
        [peripheral writeValue:self.sendData forCharacteristic:self.txCharacteristic type:CBCharacteristicWriteWithoutResponse];
    }else if([@"3c3c" isEqualToString:[value substringWithRange:NSMakeRange(1, 4)]]){
        for (NSString* macAddress in self.mCallbacks) {
            CallbackSuporvisor* wrapper = (CallbackSuporvisor*)[self.mCallbacks objectForKey:macAddress];
            if ([peripheral.name containsString:[self convertSymbolForMacAddress:wrapper.macAddress]]) {
                wrapper.cookie = @"CONNECTED";
                [wrapper.rawCallback onDeviceConnected:wrapper.cookie macAddress:wrapper.macAddress];
            }
        }
    }
}

-(void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    //Maybe need do something
}

-(void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error{
    
}

-(void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(nonnull CBPeripheral *)peripheral error:(nullable NSError *)error {
    for (NSString* macAddress in self.mCallbacks) {
        CallbackSuporvisor* wrapper = (CallbackSuporvisor*)[self.mCallbacks objectForKey:macAddress];
        if (peripheral == wrapper.mPeripheral) {
            [wrapper.rawCallback onDeviceDisconnected:wrapper.cookie macAddress:wrapper.macAddress];
            //[self.mCallbacks removeObjectForKey:wrapper.macAddress];
            //wrapper.mPeripheral = nil;
        }
    }
}

-(void)unregisterConnectionListener:(NSString *)macAddress {
    CallbackSuporvisor* listener = (CallbackSuporvisor*)[self.mCallbacks objectForKey:macAddress];
    if (listener == nil || listener.mPeripheral == nil) return;
    
    [self.mCallbacks removeObjectForKey:macAddress];
    if (self.mCallbacks.count <= 0) {
        [self.mBluetoohManager stopScan];
    }
    [self.mBluetoohManager cancelPeripheralConnection:listener.mPeripheral];
    self.mBluetoohManager = nil;
}

-(void) scheduleNextCheck {
    self.mCheckScheduled = YES;
}

-(void) scheduleNextScan {
    self.mScanScheduled = YES;
}

-(NSString *) convertSymbolForMacAddress :(NSString *)originMac{
    NSString* parseOriginMac = [originMac stringByReplacingOccurrencesOfString:@":" withString:@""];
    return parseOriginMac;
}

- (NSData *)encodeSendDataByRadomData:(NSString *)randomValue
{
    NSString *xorResult = [self pinxCreator:randomValue withPinv:FIX_DECODE_HEX_DATA];
    int hexUserID = [[[NSString alloc] initWithFormat:@"%x",[self.userID intValue]] intValue];
    NSString *hexStringUserID = [NSString stringWithFormat:@"%8.0d",hexUserID];
    NSString *sendString = [xorResult stringByAppendingString:[hexStringUserID stringByReplacingOccurrencesOfString:@" " withString:@"0"]];
    NSLog(@"sendString = %@",sendString);
    NSData *returnData = [self convert:sendString];
    NSLog(@"%@",returnData);
    return returnData;
}

//Xor
-(NSString *)pinxCreator:(NSString *)pan withPinv:(NSString *)pinv
{
    if (pan.length != pinv.length)
    {
        return nil;
    }
    const char *panchar = [pan UTF8String];
    const char *pinvchar = [pinv UTF8String];
    NSString *temp = [[NSString alloc] init];
    for (int i = 0; i < pan.length; i++)
    {
        int panValue = [self charToint:panchar[i]];
        int pinvValue = [self charToint:pinvchar[i]];
        temp = [temp stringByAppendingString:[NSString stringWithFormat:@"%X",panValue^pinvValue]];
    }
    return temp;
}

- (int)charToint:(char)tempChar
{
    if (tempChar >= '0' && tempChar <='9')
    {
        return tempChar - '0';
    }
    else if (tempChar >= 'A' && tempChar <= 'F')
    {
        return tempChar - 'A' + 10;
    }
    return 0;
}

//Convert from NSString to byte
-(NSData*) convert:(NSString*) str
{
    const char* cs = str.UTF8String;
    int count = strlen(cs);
    int8_t bytes[count / 2];
    for(int i = 0; i<count; i+=2)
    {
        char c1 = *(cs + i);
        char c2 = *(cs + i + 1);
        if(C2I(c1) >= 0 && C2I(c2) >= 0)
        {
            bytes[i / 2] = C2I(c1) * 16 + C2I(c2);
        }
        else
        {
            return nil;
        }
    }
    return [NSData dataWithBytes:bytes length:count / 2];
}
@end
